﻿using System;
using Microsoft.Xna.Framework;
using CellarDoor.Gui.Elements;
using CellarDoor.Gui.Controller;

namespace CellarDoor.Gui.Animation
{
    public class AnimateComponent : UiController
    {
        /// <summary>
        /// Gets and sets a value indicating whether this animation is looped
        /// </summary>
        public bool Loop { get; set; }

        /// <summary>
        /// Indicates the animation progress. The value ranges from 0 to 1
        /// </summary>
        public float Progress { get; protected set; }

        /// <summary>
        /// The progress value modified with the ease function
        /// </summary>
        public float EasedProgress { get; protected set; }

        /// <summary>
        /// Gets the duration of this animation in ms. This value is allowed to be
        /// shorter or longer than the whole animation.
        /// </summary>
        public float Duration { get; set; }

        /// <summary>
        /// Gets the delay before the animation starts.
        /// </summary>
        public float Delay { get; set; }

        /// <summary>
        /// Gets and sets the ease function to use.
        /// </summary>
        public EaseFunction EaseFunction { get; set; }

        /// <summary>
        /// Gets and sets a custom ease modifier
        /// </summary>
        public IEaseComponent CustomEase { get; set; }

        /// <summary>
        /// Occurs when the animation starts or resumes.
        /// </summary>
        public event AnimateEvent StartAnimation;

        /// <summary>
        /// Occurs when the animation holds on pause or stop.
        /// </summary>
        public event AnimateEvent StopAnimation;

        /// <summary>
        /// Occurs when the animation is looped and has finished one iteration. 
        /// </summary>
        public event AnimateEvent LoopAnimation;

        private bool playing;
        private float timeSpent;

        /// <summary>
        /// Gets the sum of <see cref="Duration"/> and <see cref="Delay"/>
        /// </summary>
        public float TotalDuration
        {
            get { return Duration + Delay; }
        }
        /// <summary>
        /// Creates an animation component
        /// </summary>
        /// <param name="element"></param>
        public AnimateComponent(UiElement element)
            : base(element)
        {
        }

        /// <summary>
        /// Updates the animation logic
        /// </summary>
        /// <param name="dt">The time step in ms</param>
        public override void Update(float dt)
        {
            if (playing)
            {
                timeSpent += dt;
                Progress = MathHelper.Clamp(timeSpent - Delay, 0, Duration) / Duration;

                if (timeSpent >= TotalDuration)
                {
                    if (Loop)
                    {
                        timeSpent -= TotalDuration;
                        Progress -= 1.0f;

                        if (LoopAnimation != null)
                        {
                            LoopAnimation(this);
                        }
                    }
                    else
                    {
                        playing = false;
                        Progress = 1.0f;

                        if (StopAnimation != null)
                        {
                            StopAnimation(this);
                        }
                    }
                }

                EasedProgress = Ease(Progress);
                Animate();
            }
        }

        protected virtual float Ease(float value)
        {
            switch (EaseFunction)
            {
                case EaseFunction.InQuad:
                    return EaseQuad(value);
                case EaseFunction.OutQuad:
                    return EaseQuadInverse(value);
                case EaseFunction.InOutQuad:
                    return EaseInOutQuad(value);
                case EaseFunction.InCubic:
                    return EaseInCubic(value);
                case EaseFunction.OutCubic:
                    return EaseOutCubic(value);
                case EaseFunction.InOutCubic:
                    return EaseInOutCubic(value);
                case EaseFunction.InSine:
                    return EaseInSine(value);
                case EaseFunction.OutSine:
                    return EaseOutSine(value);
                case EaseFunction.InOutSine:
                    return EaseInOutSine(value);
                case EaseFunction.InBounce:
                    return EaseInBounce(value);
                case EaseFunction.OutBounce:
                    return EaseOutBounce(value);
                case EaseFunction.InOutBounce:
                    return EaseInOutBounce(value);
                default:
                    if (CustomEase != null)
                    {
                        return CustomEase.Ease(value);
                    }
                    return EaseQuadInverse(value);
            }
        }

        protected virtual void Animate()
        {
        }

        /// <summary>
        /// Starts or resumes the animation
        /// </summary>
        public void Start()
        {
            if (!playing)
            {
                playing = true;
                if (StartAnimation != null)
                {
                    StartAnimation(this);
                }
            }
        }

        /// <summary>
        /// Holds the animation
        /// </summary>
        public void Stop()
        {
            if (playing)
            {
                playing = false;
                if (StopAnimation != null)
                {
                    StopAnimation(this);
                }
            }
        }

        /// <summary>
        /// Rewinds the animation to beginning. The animation keeps playing if it is started.
        /// </summary>
        public void Reset()
        {
            Progress = 0;
        }

        protected static float EaseQuad(float v)
        {
            return v * v;
        }

        protected static float EaseQuadInverse(float v)
        {
            return 1f - EaseQuad(1f - v);
        }

        protected static float EaseInOutQuad(float v)
        {
            if (v < 0.5f)
            {
                return 0.5f * EaseQuad(v * 2f);
            }
            else
            {
                return 0.5f * EaseQuadInverse((v - 0.5f) * 2f) + 0.5f;
            }
        }

        protected static float EaseInCubic(float v)
        {
            return v * v * v;
        }

        protected static float EaseOutCubic(float v)
        {
            return 1f - EaseInCubic(1f - v);
        }

        protected static float EaseInOutCubic(float v)
        {
            if (v < 0.5f)
            {
                return 0.5f * EaseInCubic(v * 2f);
            }
            else
            {
                return 0.5f * EaseOutCubic((v - 0.5f) * 2f) + 0.5f;
            }
        }

        protected static float EaseInSine(float v)
        {
            return -(float)Math.Cos(MathHelper.PiOver2 * v) + 1;
        }

        protected static float EaseOutSine(float v)
        {
            return 1f - EaseInSine(1f - v);
        }

        protected static float EaseInOutSine(float v)
        {
            if (v < 0.5f)
            {
                return 0.5f * EaseInSine(v * 2f);
            }
            else
            {
                return 0.5f * EaseOutSine((v - 0.5f) * 2f) + 0.5f;
            }
        }

        protected static float EaseExp(float v) 
        {
            if (v == 0)
            {
                return 0;
            }
            return (float)Math.Pow(2, 10f * (v - 1f));
	    }

        protected static float EaseExpInverse(float v) 
        {
            return 1f - EaseExp(1f - v);
	    }

        protected static float EaseInOutExp(float v)
        {
            if (v < 0.5f)
            {
                return 0.5f * EaseExp(v * 2f);
            }
            else
            {
                return 0.5f * EaseExpInverse((v - 0.5f) * 2f) + 0.5f;
            }
        }

        protected static float EaseInBounce(float v)
        {
            return 1f - EaseOutBounce(1f - v);
        }

        protected static float EaseOutBounce(float v)
        {
            if (v < (1f / 2.75f))
            {
                return (7.5625f * v * v);
            }
            else if (v < (2f / 2.75f))
            {
                v -= (1.5f / 2.75f);
                return (7.5625f * v * v + 0.75f);
            }
            else if (v < (2.5f / 2.75f))
            {
                v -= (2.25f / 2.75f);
                return (7.5625f * v * v + 0.9375f);
            }
            else
            {
                v -= (2.625f / 2.75f);
                return (7.5625f * v * v + 0.984375f);
            }
        }

        protected static float EaseInOutBounce(float v)
        {
            if (v < 0.5f)
            {
                return EaseInBounce(v * 2f) * 0.5f;
            }
            else
            {
                return EaseOutBounce(v * 2f - 1f) * 0.5f;
            }
        }
    }
}
